/**
 * @file      tcp_client.c
 * @author    wzzlyzdn (wzzlyzdn@163.com)
 * @brief     
 * @version   0.1
 * @date      2022-08-04
 * 
 * @copyright Copyright (c) 2022 wzzlyzdn
 * 
 * @note      历史记录: 
 *            - 创建初始版本
 *            - 依据编码规范重构
 * @warning   
 * @par       修改记录: 
 * <table>
 * <tr><th>date          <th>Version    <th>Author      <th>Description             </tr>
 * <tr><td>2022-08-04    <td> 0.1       <td>wzzlyzdn       <td>创建初始版本              </tr>
 * <tr><td>2022-08-12    <td> 0.2       <td>wzzlyzdn       <td>依据编码规范重构          </tr>
 * <tr><td>2022-08-15    <td> 0.3       <td>wzzlyzdn       <td>入参校验，返回值校验       </tr>
 * </table>
 */
#include "server_client.h"
#include "server.h"

extern int file_transfer_state;

static int thread_exit_code = 0;
static FILE_INFO filesend;//

clock_t t_start;
clock_t t_end;

/**
 * @fn        void *client_recv(void *fd)
 * @brief     线程函数 - 客户端接收消息
 * 
 * @param     [in] fd        用于连接服务器的 sockfd
 * 
 * @return    void*          
 */
void *client_recv(void *fd)
{
    int sockfd_client = (int)((long)fd);
    if(sockfd_client < 0)
    {
        DEBUG_INFO("err : sockfd_client 错误\n");
        thread_exit_code = 1;
    }

    int ret_recv = 0;
    pthread_t thread_filerecv;
    char recvbuf[BUFFER_SIZE + 1] = {0};

    while (1 && !thread_exit_code)
    {
        /* 此处 sizeof(recvbuf) 非常有问题 */
        ret_recv = recv(sockfd_client, recvbuf, sizeof(recvbuf), 0);
        if(ret_recv <= 0)
        {
            DEBUG_INFO("err : recv, return %d\n",ret_recv);
            thread_exit_code = 1;
        }

        recvbuf[ret_recv] = '\0';
        DEBUG("服务器：%s\n", recvbuf);

        if (!strncmp(recvbuf, "quit", sizeof("quit")) || !strncmp(recvbuf, "QUIT", sizeof("QUIT")) || !strncmp(recvbuf, "退出", sizeof("退出")))
        {
            DEBUG("退出\n");
            thread_exit_code = 1;
            DEBUG_INFO("client_recv 函数 thread_exit_code 置为 1 实际为 %d\n", thread_exit_code);
            break;
        }

        /* 接收的文件传输请求或文件传输请求允许，准备接收文件 */
        if (!strncmp(recvbuf, "$FILE_TRANSFER$", sizeof("$FILE_TRANSFER$")) ||
            !strncmp(recvbuf, "$FILE_TRANSFER_REQUEST_ALLOW$", sizeof("$FILE_TRANSFER_REQUEST_ALLOW$")))
        {
            DEBUG_INFO("FILE_TRANSFER_REQUEST_ALLOW\n");
            ret_recv = recv(sockfd_client, recvbuf, sizeof(recvbuf), 0);
            recvbuf[ret_recv] = '\0';
            DEBUG_INFO("%s\n", recvbuf);
            data_packets_parsing(&filesend, recvbuf);
            display_data_packets(filesend);
            filesend.sockfd = c_sockfd(filesend.IP, filesend.port);
            display_data_packets(filesend);
            pthread_t thread_file_recv;
            pthread_create(&thread_file_recv, NULL, file_receive, (void *)&filesend);
            DEBUG_INFO("线程退出位置\n");
        }
    }

    DEBUG_INFO("client_recv 线程退出\n");
    pthread_exit(NULL);
}

/**
 * @fn        void *client_send(void *fd)
 * @brief     线程函数 - 客户端接收信息
 * 
 * @param     [in] fd        用于连接服务器的 sockfd
 * 
 * @return    void*          
 */
void *client_send(void *fd)
{
    int sockfd_client = (int)((long)fd);
    if(sockfd_client < 0)
    {
        DEBUG_INFO("err : sockfd_client 错误\n");
        thread_exit_code = 1;
    }

    char sendbuf[BUFFER_SIZE + 1] = {0};
    filesend.sockfd = sockfd_client;
    int ret_send = 0;

    while (1 && !thread_exit_code)
    {
        scanf("%s", sendbuf);
        ret_send = send(sockfd_client, sendbuf, strlen(sendbuf), 0);
        if(ret_send <= 0)
        {
            DEBUG_INFO("err : send, return %d\n",ret_send);
            thread_exit_code = 1;
        }

        if (!strncmp(sendbuf, "quit", sizeof("quit")) || !strncmp(sendbuf, "QUIT", sizeof("QUIT")) || !strncmp(sendbuf, "退出", sizeof("退出")))
        {
            DEBUG("客户端退出\n");
            thread_exit_code = 1;
            break;
        }

        if (!strncmp(sendbuf, "$FILE_LIST_REQUEST$", sizeof("$FILE_LIST_REQUEST$")))
        {
            /* 请求文件列表 */
        }

        if (!strncmp(sendbuf, "$FILE_TRANSFER$", sizeof("$FILE_TRANSFER$")))
        {
            file_info_bzero_except_ip(&filesend);
            /* 文件传输请求 */

            /* 断点续传 */
            GOTO_FILE_TRANSFER:

            DEBUG_INFO("测试点 是否进入\n");
            if (!(FILE_TRANSFER_CONTINUE == file_transfer_state))
            {
                DEBUG("输入文件名\n");
                scanf("%s", filesend.filename);
            }
            
            data_packets_combining(filesend, sendbuf);
            display_data_packets(filesend);
            send(sockfd_client, sendbuf, strlen(sendbuf), 0);
            filesend.filesize = 0;
            file_transfer_state = FILE_TRANSFER;
        }

        if (!strncmp(sendbuf, "$FILE_TRANSFER_BREAK$", sizeof("$FILE_TRANSFER_BREAK$")))
        {
            /* 传输中断 */
            DEBUG("传输中断\n");
            file_transfer_state = FILE_TRANSFER_BREAK;
            DEBUG_INFO("file_transfer_state = %d\n",file_transfer_state);
        }

        if (!strncmp(sendbuf, "$FILE_TRANSFER_CONTINUE$", sizeof("$FILE_TRANSFER_CONTINUE$")))
        {
            file_transfer_state = FILE_TRANSFER_CONTINUE;
            DEBUG("输入文件名\n");
            scanf("%s",filesend.filename);

            get_filesize(&filesend);

            sprintf(sendbuf,"%s","$FILE_TRANSFER$");
            send(sockfd_client, sendbuf, strlen(sendbuf), 0);

            usleep(50*1000);
            goto GOTO_FILE_TRANSFER;
        }
    }
    pthread_exit(NULL);
}

/**
 * @fn        int client_init(char *ip_addr)
 * @brief     客户端初始化
 *            创建 socket , connect, 创建接收发送线程
 * 
 * @param     [in] ip_addr   服务器 IP 地址，用于 connect
 * 
 * @return    int            失败返回 -1，成功返回 sockfd
 */
int client_init(char *ip_addr)
{
    DEBUG("/* 创建 Socket */\n");
    int sockfd_client = socket(AF_INET, SOCK_STREAM, 0);

    pthread_t thread_recv;
    pthread_t thread_send;

    struct sockaddr_in client_addr;
    bzero(&client_addr, sizeof(struct sockaddr_in));

    if (-1 == sockfd_client)
    {
        DEBUG_INFO("err : 创建 Socket 失败!\n");
        return -1;
    }

    DEBUG("/* 连接 connect */\n");

    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(SERVER_PORT);
    client_addr.sin_addr.s_addr = inet_addr(ip_addr);

    if (-1 == connect(sockfd_client, (struct sockaddr *)&client_addr, sizeof(struct sockaddr)))
    {
        close(sockfd_client);
        DEBUG_INFO("err : 连接失败!\n");
        return -1;
    }

    if (pthread_create(&thread_recv, NULL, client_recv, (void *)((long)sockfd_client)))
    {
        DEBUG_INFO("err : 接收线程创建失败\n");
        return -1;
    }

    if (pthread_create(&thread_send, NULL, client_send, (void *)((long)sockfd_client)))
    {
        DEBUG_INFO("err : 发送线程创建失败\n");
        return -1;
    }

    pthread_detach(thread_recv);
    pthread_detach(thread_send);

    return sockfd_client;
}

/**
 * @fn        int get_filesize(FILE_INFO *db)
 * @brief     获取文件大小，将其存入 FILE_INFO 结构体 filesize 成员中
 * 
 * @param     [in,out] db    FILE_INFO 结构体
 * 
 * @return    int            失败返回 -1；成功返回 0
 */
int get_filesize(FILE_INFO *db)
{
    FILE *fp = fopen(db->filename, "rb");
    if(NULL == fp)
    {
        DEBUG_INFO("文件 %s 打开失败\n",db->filename);
        return -1;
    }

    fseek(fp, 0, SEEK_END);
    db->filesize = ftell(fp);

    DEBUG_INFO("db->filesize = ftell(fp) + 1 = %ld\n", db->filesize);
    fclose(fp);

    return 0;
}

/**
 * @fn        int tcp_client_main(int argc, char *argv[])
 * @brief     TCP 客户端函数 处理客户端初始化信息，处理返回值与错误信息
 * 
 * @param     [in] argc      
 * @param     [in] argv      argv[1] 服务器 IP 地址
 * 
 * @return    int            返回 0
 */
int tcp_client_main(int argc, char *argv[])
{
    DEBUG("---————————————————=☆=————————————————---\n");
    int sockfd_client = client_init(argv[1]);

    bzero(&filesend, sizeof(filesend));
    sprintf(filesend.IP, "%s", argv[1]);

    while (1)
    {
        if (-1 == sockfd_client)
        {
            DEBUG_INFO("client_init 失败!\n");
            break;
        }
        if (thread_exit_code)
        {
            DEBUG("客户端终止!\n");
            break;
        }
    }
    close(sockfd_client);
    DEBUG("---————————————————=☆=————————————————---\n");
    
    return 0;
}